package bufio

import "core:io"
import "core:mem"
import "core:unicode/utf8"
// import "core:bytes"

// Writer is a buffered wrapper for an io.Writer
Writer :: struct {
	buf:            []byte,
	buf_allocator:  mem.Allocator,

	wr: io.Writer,
	n: int,

	err: io.Error,

	max_consecutive_empty_writes: int,

}

// Initialized a Writer with an `allocator`
writer_init :: proc(b: ^Writer, wr: io.Writer, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
	size := size
	size = max(size, MIN_READ_BUFFER_SIZE)
	writer_reset(b, wr)
	b.buf_allocator = allocator
	b.buf = make([]byte, size, allocator)
}

// Initialized a Writer with a user provided buffer `buf`
writer_init_with_buf :: proc(b: ^Writer, wr: io.Writer, buf: []byte) {
	writer_reset(b, wr)
	b.buf_allocator = {}
	b.buf = buf
}

// writer_destroy destroys the underlying buffer with its associated allocator IFF that allocator has been set
writer_destroy :: proc(b: ^Writer) {
	delete(b.buf, b.buf_allocator)
	b^ = {}
}

// writer_size returns the size of underlying buffer in bytes
writer_size :: proc(b: ^Writer) -> int {
	return len(b.buf)
}

writer_reset :: proc(b: ^Writer, w: io.Writer) {
	b.wr = w
	b.n = 0
	b.err = nil
}


// writer_flush writes any buffered data into the underlying io.Writer
writer_flush :: proc(b: ^Writer) -> io.Error {
	if b.err != nil {
		return b.err
	}
	if b.n == 0 {
		return nil
	}

	n, err := io.write(b.wr, b.buf[0:b.n])
	if n < b.n && err == nil {
		err = .Short_Write
	}
	if err != nil {
		if n > 0 && n < b.n {
			copy(b.buf[:b.n-n], b.buf[n : b.n])
		}
		b.n -= n
		b.err = err
		return err
	}
	b.n = 0
	return nil
}

// writer_available returns how many bytes are unused in the buffer
writer_available :: proc(b: ^Writer) -> int {
	return len(b.buf) - b.n
}

// writer_buffered returns the number of bytes that have been writted into the current buffer
writer_buffered :: proc(b: ^Writer) -> int {
	return b.n
}

// writer_write writes the contents of p into the buffer
// It returns the number of bytes written
// If n < len(p), it will return an error explaining why the write is short
writer_write :: proc(b: ^Writer, p: []byte) -> (n: int, err: io.Error) {
	p := p
	for len(p) > writer_available(b) && b.err == nil {
		m: int
		if writer_buffered(b) == 0 {
			m, b.err = io.write(b.wr, p)
			if m < 0 && b.err == nil {
				b.err = .Negative_Write
				break
			}
		} else {
			m = copy(b.buf[b.n:], p)
			b.n += m
			writer_flush(b)
		}
		n += m
		p = p[m:]
	}
	if b.err != nil {
		return n, b.err
	}
	m := copy(b.buf[b.n:], p)
	b.n += m
	m += n
	return m, nil
}

// writer_write_byte writes a single byte
writer_write_byte :: proc(b: ^Writer, c: byte) -> io.Error {
	if b.err != nil {
		return b.err
	}
	if writer_available(b) <= 0 && writer_flush(b) != nil {
		return b.err
	}
	b.buf[b.n] = c
	b.n += 1
	return nil
}

// writer_write_rune writes a single unicode code point, and returns the number of bytes written with any error
writer_write_rune :: proc(b: ^Writer, r: rune) -> (size: int, err: io.Error) {
	if r < utf8.RUNE_SELF {
		err = writer_write_byte(b, byte(r))
		size = 0 if err != nil else 1
		return
	}
	if b.err != nil {
		return 0, b.err
	}

	buf: [4]u8

	n := writer_available(b)
	if n < utf8.UTF_MAX {
		writer_flush(b)
		if b.err != nil {
			return 0, b.err
		}
		n = writer_available(b)
		if n < utf8.UTF_MAX {
			// this only happens if the buffer is very small
			w: int
			buf, w = utf8.encode_rune(r)
			return writer_write(b, buf[:w])
		}
	}

	buf, size = utf8.encode_rune(r)
	copy(b.buf[b.n:], buf[:size])
	b.n += size
	return
}

// writer_write_string writes a string into the buffer
// It returns the number of bytes written
// If n < len(p), it will return an error explaining why the write is short
writer_write_string :: proc(b: ^Writer, s: string) -> (int, io.Error) {
	return writer_write(b, transmute([]byte)s)
}

// writer_read_from is to support io.Reader_From types
// If the underlying writer supports the io,read_from, and b has no buffered data yet,
// this procedure calls the underlying read_from implementation without buffering
writer_read_from :: proc(b: ^Writer, r: io.Reader) -> (n: i64, err: io.Error) {
	if b.err != nil {
		return 0, b.err
	}
	for {
		if writer_available(b) == 0 {
			writer_flush(b) or_return
		}
		if b.max_consecutive_empty_writes <= 0 {
			b.max_consecutive_empty_writes = DEFAULT_MAX_CONSECUTIVE_EMPTY_READS
		}

		m: int
		nr := 0
		for nr < b.max_consecutive_empty_writes {
			m, err = io.read(r, b.buf[b.n:])
			if m != 0 || err != nil {
				break
			}
			nr += 1
		}
		if nr == b.max_consecutive_empty_writes {
			return n, .No_Progress
		}
		b.n += m
		n += i64(m)
		if err != nil {
			break
		}
	}

	if err == .EOF {
		if writer_available(b) == 0 {
			err = writer_flush(b)
		} else {
			err = nil
		}
	}
	return
}



// writer_to_stream converts a Writer into an io.Stream
writer_to_stream :: proc(b: ^Writer) -> (s: io.Stream) {
	s.data = b
	s.procedure = _writer_proc
	return
}

// writer_to_stream converts a Writer into an io.Stream
writer_to_writer :: proc(b: ^Writer) -> (s: io.Writer) {
	return writer_to_stream(b)
}



_writer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
	b := (^Writer)(stream_data)
	#partial switch mode {
	case .Flush:
		err = writer_flush(b)
		return
	case .Write:
		n_int: int
		n_int, err = writer_write(b, p)
		n = i64(n_int)
		return
	case .Destroy:
		writer_destroy(b)
		return
	case .Query:
		return io.query_utility({.Flush, .Write, .Destroy, .Query})
	}
	return 0, .Unsupported
}
